<html>
    <head>
        <title>分布式事件总线</title>
    </head>
    <body>
        <script>
            // 分布式事件总线

                    /*
                        分布式事件总线允许 "发布" 和 "订阅" 可以跨应用/服务边界的事件。

                        你可以使用分布式事件总线在微服务或应用程序之间异步发送和接收消息。
                    */

                    /*
                        一、提供程序  Providers

                                分布式事件总线系统提供了一个抽象，可以由任何提供程序(提供商)实现。

                                现在，有四个开箱即用的提供程序：

                                     1、LocalDistributedEventBus  是abp中实现分布式事件总线作为进程内工作的默认实现。
                                                                  是的！ 如果你没有配置真正的分布式提供程序，默认实现就像本地事件总线一样工作。

                                     2、AzureDistributedEventBus  使用Azure服务总线实现分布式事件总线。请参阅Azure服务总线集成文档以了解如何配置它。

                                     3、RabbitMqDistributedEventBus 使用RabbitMQ实现分布式事件总线。请参阅RabbitMQ集成文档了解如何配置它。

                                     4、KafkaDistributedEventBus    使用Kafka实现分布式事件总线。请参阅Kafka集成文档以了解如何配置它。

                                     5、RebusDistributedEventBus    使用Rebus实现分布式事件总线。请参阅Rebus集成文档以了解如何配置它。


                                默认使用本地分布式事件总线有几个重要的优点。

                                最重要的一个是： 它允许你编写与分布式架构兼容的代码。你现在可以编写一个单体应用程序，以后可以拆分为微服务。

                                通过"本地分布式事件总线" 而不是 "本地事件总线"在 "有界上下文（bounded context"之间(或应用程序模块之间)通信是一种很好的做法。

                                例如： 预构建的应用程序模块被设计为在分布式系统中作为服务工作，同时它们也可以在单体应用程序中作为模块工作，而无需依赖外部消息代理。

                    */

                    /*
                        二、发布分布式事件  Publishing Events
                        
                                以下部分解释了发布分布式事件的两种方法：

                                    1、使用IDistributedEventBus 发布事件

                                                ①、如何发布事件
                                                
                                                                IDistributedEventBus可以注入并用于发布分布式事件。

                                                                eg:  当产品的库存数量发生变化时发布分布式事件

                                                                        using System;
                                                                        using System.Threading.Tasks;
                                                                        using Volo.Abp.DependencyInjection;
                                                                        using Volo.Abp.EventBus.Distributed;

                                                                        namespace AbpDemo
                                                                        {
                                                                            public class MyService : ITransientDependency
                                                                            {
                                                                                private readonly IDistributedEventBus _distributedEventBus;

                                                                                public MyService(IDistributedEventBus distributedEventBus)
                                                                                {
                                                                                    _distributedEventBus = distributedEventBus; // 注入分布式事件总线服务
                                                                                }
                                                                                
                                                                                public virtual async Task ChangeStockCountAsync(Guid productId, int newCount)
                                                                                {
                                                                                    await _distributedEventBus.PublishAsync( // 发布分布式事件
                                                                                        new StockCountChangedEto  // 传入事件事件传输对象
                                                                                        {
                                                                                            ProductId = productId,
                                                                                            NewCount = newCount
                                                                                        }
                                                                                    );
                                                                                }
                                                                            }
                                                                        }

                                                                PublishAsync方法获取事件对象，该对象负责保存与事件相关的数据。它是一个简单的普通类：

                                                                        using System;

                                                                        namespace AbpDemo
                                                                        {
                                                                            [EventName("MyApp.Product.StockChange")]
                                                                            public class StockCountChangedEto
                                                                            {
                                                                                public Guid ProductId { get; set; }
                                                                                
                                                                                public int NewCount { get; set; }
                                                                            }
                                                                        }

                                                                即使你不需要传输任何数据，你也需要创建一个类（在这种情况下是一个空类）。


                                                        ★、Eto是我们按惯例使用的EventTransferObject的后缀。虽然不是必需的，但我们发现识别此类事件类很有用（就像应用层上的DTO一样）。


                                                ②、[EventName] 特性

                                                                EventName特性是可选的，但是建议使用它。

                                                                如果你不为事件类型使用它(ETO类)，则事件名称将是事件类的全名，AbpDemo.StockCountChangedEto在这种情况下。


                                                ③、关于事件对象的序列化

                                                                事件传输对象（ETO）必须是可序列化的，因为当它被传输到进程外时，它们将被序列化/反序列化为JSON或其他格式。

                                                                避免循环引用、多态、私有setter并提供默认（空）构造函数，
                                                                
                                                                如果你有任何其他构造函数作为良好的实践（而某些序列化程序可能会容忍它），就像DTO一样。

                                
                                2、在实体/聚合根类中发布事件

                                            实体不能通过依赖注入注入服务，但在实体/聚合根中发布分布式事件是很常见的。

                                        ①、如何在实体/聚合根中发布事件

                                                    eg： 在聚合根方法中发布分布式事件

                                                            using System;
                                                            using Volo.Abp.Domain.Entities;

                                                            namespace AbpDemo
                                                            {
                                                                public class Product : AggregateRoot<Guid>
                                                                {
                                                                    public string Name { get; set; }
                                                                    
                                                                    public int StockCount { get; private set; }

                                                                    private Product() { }

                                                                    public Product(Guid id, string name)
                                                                        : base(id)
                                                                    {
                                                                        Name = name;
                                                                    }

                                                                    // 带有分布式事件的属性更改方法
                                                                    public void ChangeStockCount(int newCount)
                                                                    {
                                                                        StockCount = newCount;
                                                                        
                                                                        // 添加(发布)分布式事件
                                                                        AddDistributedEvent(
                                                                            new StockCountChangedEto
                                                                            {
                                                                                ProductId = Id,
                                                                                NewCount = newCount
                                                                            }
                                                                        );
                                                                    }
                                                                }
                                                            }

                                                   ★、 AggregateRoot类定义了AddDistributedEvent以添加一个新的分布式事件，该事件在聚合根对象保存（创建、更新或删除）到数据库时发布。


                                                   ★、如果实体发布了这样的事件，最好以受控的方式更改相关属性，就像上面的示例-StockCount只能通过保证发布事件的ChangeStockCount方法进行更改。

                                        ②、IGeneratesDomainEvents接口 

                                                    实际上，添加分布式事件并不是AggregateRoot类独有的。

                                                    你可以为任何实体类实现IGeneratesDomainEvents。

                                                    但是，AggregateRoot默认实现它，让你轻松。

                                                    
                                                    ★、不建议为非聚合根的实体实现此接口，因为它可能不适用于此类实体的某些数据库提供程序。它适用于EF Core，但不适用于MongoDB。

                                                    EF Core可以用！！ 我得用！！

                                        ③、工作原理

                                                    调用AddDistributedEvent不会立即发布事件。  
                                                    
                                                    当你将更改保存到数据库时，事件将被发布：

                                                         对于EF Core，它发布在DbContext.SaveChanges上。

                                                         对于MongoDB，当你调用存储库的InsertAsync、UpdateAsync或DeleteAsync方法时会发布它（因为MongoDB没有更改跟踪系统）。


                    */


                    /*
                        三、订阅分布式事件

                                    服务可以实现IDistributedEventHandler<TEvent>来处理事件。

                                    eg: 处理上面定义的StockCountChangedEto

                                            using System.Threading.Tasks;
                                            using Volo.Abp.DependencyInjection;
                                            using Volo.Abp.EventBus.Distributed;

                                            namespace AbpDemo
                                            {
                                                public class MyHandler
                                                    : IDistributedEventHandler<StockCountChangedEto>,  // 实现分布式事件处理程序接口(泛型)
                                                    ITransientDependency
                                                {
                                                    public async Task HandleEventAsync(StockCountChangedEto eventData)
                                                    {
                                                        var productId = eventData.ProductId;
                                                    }
                                                }
                                            } 

                                            MyHandler被ABP自动发现，并且每当发生StockCountChangedEto事件时，都会调用HandleEventAsync

                                            如果你使用分布式消息代理，例如RabbitMQ，ABP会自动订阅消息代理上的事件，获取消息，执行处理程序。

                                            如果事件处理程序成功执行（没有抛出任何异常），它会向消息代理发送确认（确认字符）。

                                    你可以在这里注入任何服务并执行任何所需的逻辑。

                                    单个事件处理程序类可以订阅多个事件，但为每种事件类型实现IDistributedEventHandler<TEvent>接口。


                                    如果你执行数据库操作并使用事件处理程序内的存储库，你可能需要创建一个工作单元，因为服务中是没有工作单元的，它不是控制器的一部分。

                                    此时，把处理方法定义为 virtual,并为该方法加上 [UnitOfWork]特性，或者手动使用 IUnitOfWorkManager创建一个工作单元。

                                    ★、事件处理程序必须注册到IOC容器中！！
                                    
                    */

                    /*
                        四、监控分布式事件
                        
                                    ABP允许你在应用程序"发布" 或 "接收" 分布式事件时随时了解事件的情况。

                                    此功能使你能够跟踪应用程序之之间的事件流，并根据接收或发送的分布式事件采取适当的操作。


                                    1、接收事件()
                                            
                                            DistributedEventReceived abp内置本地事件类型,当本地应用程序从分布式事件总管接收到事件时触发此事件，可用于一些监控操作。

                                            DistributedEventReceived本地事件会在应用程序从分布式事件总线接收到事件时被触发。

                                            DistributedEventReceived事件类具有以下字段：

                                                ①、Source：    它代表分布式事件的来源。来源可以是Direct、Inbox、Outbox

                                                ②、EventName： 表示接收到的事件的名称。

                                                ③、EventData：  它表示与接收到的事件关联的实际数据。由于它是object类型，因此它可以保存任何类型的数据。

                                            eg: 当你的应用程序从分布式事件总线接收到事件时获得通知

                                                // 订阅、处理 接收分布式事件的本地事件
                                                public class DistributedEventReceivedHandler : ILocalEventHandler<DistributedEventReceived>, ITransientDependency
                                                {
                                                    public async Task HandleEventAsync(DistributedEventReceived eventData)
                                                    {
                                                        // TODO: IMPLEMENT YOUR LOGIC...
                                                    }
                                                }


                                    2、发送事件

                                            DistributedEventSent abp内置本地事件类型，当本地应用程序向分布式事件总线发送事件时触发此事件，可用于一些监控事件发送的操作。

                                            DistributedEventSent类具有以下字段：

                                                ①、Source：它代表分布式事件的来源。来源可以是Direct、Inbox、Outbox

                                                ②、EventName：它代表发送的事件的名称。

                                                ③、EventData：它表示与发送的事件关联的实际数据。由于它是object类型，因此它可以保存任何类型的数据

                                                eg: 当你的应用程序将事件发送到分布式事件总线时获得通知：

                                                    // 订阅、处理 发送分布式事件的本地事件
                                                    public class DistributedEventSentHandler : ILocalEventHandler<DistributedEventSent>, ITransientDependency
                                                    {
                                                        public async Task HandleEventAsync(DistributedEventSent eventData) // 事件数据
                                                        {
                                                            // TODO: IMPLEMENT YOUR LOGIC...
                                                        }
                                                    }

                                    通过订阅 DistributedEventSent 和 DistributedEventReceived 这两个ABP内置本地事件，你可以将对分布式事件的跟踪功能无缝集成到你的应用程序中。

                                    这是你能够有效的监控消息传递流，诊断任何潜在问题，并获得对分布式消息传递系统行为的宝贵见解。
                    */



                    /*
                        五、预定义分布式事件

                                  一旦你对实体进行配置，ABP会自动为 创建、更新、删除 等操作发布分布式事件。
                                  
                                  1、预定义分布式事件类型：

                                                    有三种预定义的分布式事件类型：
                                                                
                                                            ①、EntityCreatedEto<T>在创建类型为T的实体时发布。

                                                            ②、EntityUpdatedEto<T>在更新类型为T的实体时发布。

                                                            ③、EntityDeletedEto<T>在删除类型为T的实体时发布。

                                            这些事件类型是泛型的。 T 实际上是 EventTransferObjec (ETO)的类型，而不是实体的类型。
                                            因为，实体对象不能作为事件数据的一部分进行传输。
                                            因此，通常为实体类定义一个ETO类，如 Product实体的 ProductEto


                                   2、订阅预定义分布式事件

                                           订阅自动事件与订阅常规分布式事件相同。
                                           
                                           eg: 一旦产品更新，就会收到通知：

                                                using System.Threading.Tasks;
                                                using Volo.Abp.DependencyInjection;
                                                using Volo.Abp.Domain.Entities.Events.Distributed;
                                                using Volo.Abp.EventBus.Distributed;

                                                namespace AbpDemo
                                                {
                                                    public class MyHandler : 
                                                        IDistributedEventHandler<EntityUpdatedEto<ProductEto>>, // 订阅分布式实体更新事件
                                                        ITransientDependency
                                                    {
                                                        // 分布式事件处理逻辑
                                                        public async Task HandleEventAsync(EntityUpdatedEto<ProductEto> eventData)
                                                        {
                                                            var productId = eventData.Entity.Id;
                                                            //TODO
                                                        }
                                                    }
                                                }

                                            MyHandler实现了IDistributedEventHandler<EntityUpdatedEto<ProductEto>>。
                                            
                                            需要将处理程序类注册到依赖注入系统。像在本例中一样实现ITransientDependency是一种简单的方法。


                                   3、配置预定义分布式事件系统 （哪些实体需要发布分布式事件?需要在模块类中配置）

                                            你可以在模块的ConfigureServices中配置 "AbpDistributedEntityEventOptions" 以添加选择器。

                                            eg:  
                                                Configure<AbpDistributedEntityEventOptions>(options =>
                                                {
                                                    // 所有实体都发布"实体分布式事件"
                                                    options.AutoEventSelectors.AddAll();

                                                    // 指定单个实体发布"实体分布式事件"
                                                    options.AutoEventSelectors.Add<Product>();

                                                    // 指定某个命名空间下的所有实体发布"实体分布式事件"
                                                    options.AutoEventSelectors.AddNamespace("MyProject.Products");

                                                    // 通过lambda表达式操作反射来添加选择器，自定义过滤逻辑
                                                    options.AutoEventSelectors.Add(
                                                        type => type.Namespace.StartsWith("MyProject.")
                                                    );
                                                });

                                                最后一个提供了灵活性来决定是否应为给定实体类型发布事件。返回true以接受Type。

                                                你可以添加多个选择器。如果其中一个选择器与实体类型匹配，则选择它。

                                                也就是说选择器与选择器之间是 "或" 的关系。

                                    
                                    4、事件传输多想  Eto

                                             为实体启用"自动分布式事件"后， ABP开始发布有关实体更改的事件。

                                             此时，如果你没有为实体指定相应的 EventTransferObject (Eto)，则ABP会使用名为 EntityEto的标准类型，

                                             名为"EntityEto"的标准类型有两个属性：
                                                        
                                                        ①、EntityType（string）：实体类的全名（包括命名空间）。

                                                        ②、KeysAsString（string）：更改实体的主键。如果它有一个键，则此属性将是主键值。对于复合键，它将包含由,（逗号）分隔的所有键。

                                             因此，你可以实现IDistributedEventHandler<EntityUpdatedEto<EntityEto>>来订阅更新事件。但是，订阅这种通用事件不是一个号办法，

                                             因为你将会在单个处理程序中处理所有实体的更新事件(因为他们都是用相同的Eto，所以时间类型EntityCreatedEto<T>也一样了).

                                             所以，你需要尽量为每个实体定义自己的Eto。


                                             eg: 声明对 Product 实体使用 ProductEto

                                                    public class ProductEto
                                                    {
                                                        public Guid Id { get; set; }
                                                        public string Name { get; set; }
                                                        public float Price { get; set; }
                                                    }
                                            然后使用 AbpDistributedEntityEventOptions 选项将 Product 类映射到 ProductEto

                                                    Configure<AbpDistributedEntityEventOptions>(options =>
                                                    {
                                                        options.AutoEventSelectors.Add<Product>();
                                                        options.EtoMappings.Add<Product, ProductEto>();  // 在模块类中配置实体到Eto的映射
                                                    });

                                                在这个例子中：
                                                    
                                                      添加选择器以允许发布Product实体的创建、更新和删除事件。

                                                      配置为使用ProductEto作为为Product相关事件发布的事件传输对象。
                                                     
                                            分布式事件系统使用 "对象映射系统"将 Product 实体对象映射到 ProductEto对象。(所以，你需要配置AutoMapper映射文档！！！)
                    */

                    /*
                        六、实体同步器 EntitySynchronizer基类

                                在分布式(或微服务)系统中，通常为另一个服务的实体类型订阅更改事件，因此你可以在订阅的实体更改时收到通知。

                                在这种情下，你可以使用ABP的预定义分布式事件，如上一节所述。


                                如果你的目的是存储远程实体的本地副本，你通常会订阅 创建、更新 和 删除等内置事件，并子事件处理程序中更新本地数据库。

                                ABP提供了一个预构建的 EntitySynchronizer基类，来方便你的操作。


                                现在，假设Catalog(商品清单服务)微服务中右边一个 Product实体 (可能是一个聚合根实体)，并且你希望使用本地OrderProduct实体来将
                                产品的副本保存在Order微服务中。
                                在实践中，OrderProduct类的属性将是Product属性的子集，因为并非所有的产品数据都需要在Order微服务中.此外，OrderProduct实体可能具有其他属性，
                                这些属性在Order微服务中填充和使用。


                                建议同步的第一步是在Catalog（商品清单服务）微服务中定义一个Eto类，用于传输事件数据。
                                
                                假设Product实体有一个GUID主键，你的Eto可以如下所示：

                                            [EventName("product")]
                                            public class ProductEto : EntityEto<Guid> // 标准Eto基类
                                            {
                                                // Your Product properties here...
                                            }

                                ProductEto可以放在Order(商品订购服务)微服务和 Catalog(商品清单服务) 微服务引用的 "共享项目(DLL)"中。

                                或者，如果你不想在服务之间引入常见的项目依赖，你可以将 ProductEto的副本放在Order微服务中。

                                在这种情况下，EventName 属性对于跨越两个服务映射 ProuductEto类变得至关重要 (发布和订阅时需要使用相同的事件名称);

                                定义Eto类之后，你应该将ABP配置为 Product 实体发布自动分分布式事件(创建、更新、删除)，如上一节所述：
                                                
                                            Configure<AbpDistributedEntityEventOptions>(options =>
                                            {
                                                options.AutoEventSelectors.Add<Product>();  // 允许 Product实体发布自动分布式事件
                                                options.EtoMappings.Add<Product, ProductEto>(); // 映射实体到Eto

                                            });

                                最后，你应该在Order微服务中创建一个类，派生自 EntitySynchronizer类（实体同步器类）：

                                            public class ProductSynchronizer : EntitySynchronizer<OrderProduct, Guid, ProductEto> // 集成实体同步器基类
                                            {
                                                public ProductSynchronizer(
                                                    IObjectMapper objectMapper,  // 注入 AutoMapper
                                                    IRepository<OrderProduct, Guid> repository // 注册存储
                                                    ) : base(objectMapper, repository)
                                                {
                                                }
                                            }

                                此类的要点是他订阅源实体的自动分布式事件(创建、更新、删除)，并更新本地数据库实体。

                                它适用 ObjectMapper 系统从 ProductEto 对象创建或更新 OrderProduct对象。

                                因此，你还应该配置对象映射文档以让其正常工作。

                                
                                如果你的实体有复合主键，Name你应该从EntitySynchronizer<TEntity,TSourceEntityEto> 类集成 (只是不要使用前面例子中的Guid参数)，
                                
                                并实现 FindLocalEntityAsync 以使用数据库存储在本地数据库中查找实体。

                                EntitySynchronizer与实体版本控制系统兼容（请参阅实体文档）。因此，即使接收到的事件是无序的，它也能按预期工作。
                                如果本地数据库中的实体版本比接收到的事件中的实体更新，则该事件将被忽略。
                                你应该为实体和ETO类实现IHasEntityVersion接口（对于本例，你应该为Product、ProductEto和OrderProduct类实现）。
                                如果要忽略某些类型的更改事件，可以在类的构造函数中设置IgnoreEntityCreatedEvent、IgnoreEntityUpdatedEvent和IgnoreEntityDeletedEvent。
                                
                                         eg：
                                            public class ProductSynchronizer 
                                                : EntitySynchronizer<OrderProduct, Guid, ProductEto>
                                            {
                                                public ProductSynchronizer(
                                                    IObjectMapper objectMapper,
                                                    IRepository<OrderProduct, Guid> repository
                                                    ) : base(objectMapper, repository)
                                                {
                                                    IgnoreEntityDeletedEvent = true;  // EntitySynchronizer基类中固有属性
                                                }
                                            }
                                        
                                        EntitySynchronizer 实体同步器基类中固有属性：
                                                ①、IgnoreEntityCreatedEvent（bool）
                                                ②、IgnoreEntityCreatedEvent (bool)
                                                ③、IgnoreEntityDeletedEvent (bool)

                                请注意，只有在你使用实体后，EntitySynchronizer才会自动创建、更新实体。如果你有带有存量数据的现有系统，你需要手动复制一次数据。
                                                
                    */

                    /*
                        七、事务和异常处理


                                1、使用LocalDistributedEventBus时(分布式事件总线在进程内和本地事件总线一样工作时)

                                        分布式事件总线在进程内工作(默认实现是LocalDistributedEventBus),除非你配置实际的提供程序(例如 K阿发卡 或 RabbitMQ).

                                        进程内事件总线总是"在发布事件的同一工作单元内执行事件处理程序"。

                                        这意味着，如果事件处理程序抛出异常，则相关的工作单元(数据库事务)将回滚。

                                        通过这种方式，你的"应用程序逻辑" 和 "事件处理逻辑" 变得事务性(原子性)且一致。

                                        如果你想忽略事件处理程序中的错误，你必须在处理程序中使用 try-catch 块，并且不应该重新抛出异常。

                                2、使用实际的分布式事件总线时

                                        当你切换到实际的分布式事件总线提供程序(例如 Kafka 或 RabbitMQ) 时，事件处理程序与发布者将在不同的进程(应用程序、微服务)中执行，因为它们的目的是创建分布式系统。

                                        在这种情况下，实现事务性事件发布的唯一方法是使用　＂发件箱/收件箱＂模式，如Outbox/Inbox for Transactional Events部分所述。

                                       
                                如果你不配置发件箱/收件箱模式或使用 LocalDistributedEventBus,则默认情况下，事件将在工作单元结束时发布，
                                
                                就在工作单元完成之前(这意味着在事件处理程序中抛出异常仍然回滚工作单元)，即使你在工作单元中间发布事件也是如此。

                                如果你不想等到工作单元的最后才发布事件，想立即发布事件，你可以使用IDistributedEventBus.PublishAsync方法。


                                 如果要立即执行处理程序，请将onUnitOfWorkComplete 参数设置为 false.

                            ★、在实体/聚合根类中发布事件时，onUnitOfWorkComplete选项不可用（请参阅在实体/聚合根类中发布事件部分）。
                    */

                    /*
                        八、分布式事务事件的 发件箱/收件箱模式

                                "事务性发件箱模式"用于在操作应用程序数据库的"相同事务"中发布分布式事件。

                                当你启用发件箱时，分布式事件将你的数据更改一起保存到同一事务内的数据库中，然后由带有"重试系统"的单独后台作业发送到实际的消息代理。

                                 通过这种方式，它确保了你的数据库状态和已发布事件之间的一致性。


                                 另一方面，事务性收件箱模式首先将"传入事件"保存的数据库中,然后(接收端应用程序的后台作业)以事务性方式执行事件处理程序，

                                 并从同意事务中的收件箱队列中删除事件。

                                 它通过将处理后的消息保留一段时间并丢弃从消息代理接收到的重复事件来确保事件只执行一次。


                                 启用事件发件箱和收件箱系统需要你的应用程序的一些手动步骤。请使用以下部分的说明来让它工作：

                                 ★、发件箱和收件箱可以单独启用和配置，因此如果需要，你可以只用其中一个。


                                 1、前置要求

                                            当你运行应用程序/服务的多个实例时，件箱/收件箱系统使用分布式锁系统来处理并发。因此，你应该使用本文档中解释的之一配置分布式锁系统。

                                            发件箱/收件箱系统支持Entity Framework Core（EF Core）和MongoDB数据库提供程序开箱即用。

                                            因此，你的应用程序应该使用这些数据库提供程序之一。对于其他数据库提供程序，请参阅实现自定义数据库提供程序部分。

                                  2、启用事件发件箱

                                            启用事件发件箱取决于你的数据库提供程序。

                                            ①、为 EF Core 启用事件发件箱、

                                                    打开你的DbContext 类，实现 IHasEventOutBox 接口。你最终应该通过将DbSet属性添加到你的DbContext类中：、

                                                            public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; }

                                                    在DbContext类的 OnModelCreating方法中添加一下行：

                                                            builder.ConfigureEventOutBox();

                                                    使用标准的 Add-Migration 和 Update-DataBase 命令将更改应用到数据库中。
                                                    
                                                    如果要使用命行终端，请在数据库继承项目根目录下运行以下命令：
                                                        
                                                            dotnet ef migrations add "Added_Event_Outbox"
                                                            dotnet ef database update
                                                    最后，在模块类的 ConfigureServices方法中变一下以下代码进行配置(将YourDbContext替换成你自己的DbContext类)：

                                                             Configure<AbpDistributedEventBusOptions>(options =>  // 分布式事件总线配置类
                                                            {
                                                                options.Outboxes.Configure(config =>  // 配置分布式发件箱
                                                                {
                                                                    config.UseDbContext<YourDbContext>(); // 配置针对的数据库
                                                                });
                                                            });

                                            ②、为MongoDB启用事件发件箱

                                                    。。。。。。。

                                            ③、发件箱分布式锁：

                                                        ★、重要注意：

                                                        发件箱服务使用分布式锁，以确保只有你的应用程序的单个实例同时消耗发件箱队列。

                                                        分布式锁定键应该是每个数据库唯一的。 
                                                        
                                                        config对象（在options.Outboxes.Configure(...)方法中）有DatabaseName属性，该属性用于分布式锁定键以确保唯一性。

                                                        DatabaseName由UseDbContext方法自动设置，从YourDbContext类的ConnectionStringName属性中获取库名。

                                                        因此，如果你的系统中有多个数据库，请确保对同一数据库使用相同的连接字符串名称，但对不同的数据库使用不同的连接字符串名称。

                                                        如果不能确保，可以手动设置config.DatabaseName（在UseDbContext行之后）以确保唯一性。


                                    3、启用事件收件箱

                                            启用事件发件箱取决于你的数据库提供程序。

                                            ①、为 EF Core  启用事件收件箱

                                                    打开你的DbContext类，实现 IHasEventOutBox接口。你最终应该通过DbSet属性添加到你的DbContext类中：

                                                          public DbSet<OutgoingEventRecord> IncomingEvents  { get; set; }
                                                            
                                                    在DbContext类的OnModelCreating方法中添加以下行：

                                                            builder.ConfigureEventInbox();

                                                    使用标准的Add-Migration和Update-Database命令将更改应用到数据库中。

                                                    如果要使用命令行终端，请在数据库集成项目的根目录中运行以下命令：

                                                            dotnet ef migrations add "Added_Event_Inbox"
                                                            dotnet ef database update

                                                    最后，在模块类的ConfigureServices方法中编写以下配置代码（将YourDbContext替换为你自己的DbContext类）：
                                                           
                                                            Configure<AbpDistributedEventBusOptions>(options =>  // 分布式事件总线配置类
                                                            {
                                                                options.Outboxes.Configure(config =>  
                                                                {
                                                                    config.UseDbContext<YourDbContext>();   // 配置分布式事件总线要使用的数据库
                                                                });
                                                            });

                                            ②、为 MangoDb启用启用收件箱
                                                                        
                                                            .......


                                             ③、发件箱分布式锁

                                                        重要：发件箱发送服务使用分布式锁，以确保只有你的应用程序的单个实例同时消耗发件箱队列。

                                                        分布式锁定键应该是每个数据库唯一的。

                                                        config对象（在options.Outboxes.Configure(...)方法中）具有DatabaseName属性，该属性用于分布式锁定键以确保唯一性。

                                                        DatabaseName由UseDbContext方法自动设置，从YourDbContext类的ConnectionStringName属性中获取库名。

                                                        因此，如果你的系统中有多个数据库，请确保对同一数据库使用相同的连接字符串名称，但对不同的数据库使用不同的连接字符串名称。

                                                        如果不能确保，可以手动设置config.DatabaseName（在UseDbContext行之后）以确保唯一性。
                                           
                                                        

                                    4、附加配置
                                    
                                            对于大多数情况下，默认配置就足够了。但是，你可能需要为发件箱和收件箱设置一些选项。

                                            ①、发件箱配置：

                                                        请记住发件箱是如何配置的：

                                                            Configure<AbpDistributedEventBusOptions>(options =>
                                                            {
                                                                options.Outboxes.Configure(config =>
                                                                {
                                                                    // TODO: Set options
                                                                });
                                                            });

                                                    在这里，config 对象上提提供了以下属性：

                                                        1- IsSendingEnabled（默认值：true）：
                                                                你可以设置为false以禁用向实际事件总线发送发件箱事件。
                                                                如果禁用此功能，事件仍然可以添加到发件箱，但不能发送。
                                                                如果你有多个应用程序（或应用程序实例）写入发件箱，但使用其中一个发送事件，这会很有帮助。

                                                        2- Selector： 
                                                                用于过滤要用于此配置的事件（ETO）类型的谓词。
                                                                应返回true以选择事件。
                                                                默认情况下，它会选择所有事件。
                                                                如果你想忽略发件箱中的某些ETO类型，或者想在这些配置中定义命名发件箱配置和分组事件，这尤其有用。

                                                        3-ImplementationType：
                                                                实现发件箱数据库操作的类的类型。
                                                                这通常在调用UseDbContext时设置，如前所示。
                                                                有关高级用法，请参阅实现自定义发件箱/收件箱数据库提供程序部分。

                                                        4-DatabaseName ：
                                                                用于此发件箱配置的数据库的唯一库名。请参阅启用事件发件箱/收件箱部分末尾的重要段落。

                                            
                                            ②、收件箱配置

                                                        请记住收件箱是如何配置的：

                                                            Configure<AbpDistributedEventBusOptions>(options =>
                                                            {
                                                                options.Inboxes.Configure(config =>
                                                                {
                                                                    // TODO: Set options
                                                                });
                                                            });

                                                    在这里，config对象上提供了以下属性：

                                                            1、IsProcessingEnabled（默认值：true）：
                                                                
                                                                 你可以设置为false以禁用收件箱中的处理（处理）事件。
                                                                 如果禁用此功能，事件仍然可以接收，但不能执行。
                                                                 如果你有多个应用程序（或应用程序实例），但使用其中一个来执行事件处理程序，这会很有帮助。

                                                            2、EventSelector：
                                                                
                                                                  用于过滤要用于此配置的事件（ETO）类型的谓词。
                                                                  如果你想忽略收件箱中的某些ETO类型，或者想在这些配置中定义命名收件箱配置和组事件，这将特别有用。
                                                             
                                                            3、HandlerSelector
                                                                    
                                                                  用于过滤用于此配置的事件处理类型（实现IDistributedEventHandler<TEvent>接口的类）的谓词。
                                                                  。如果你想从收件箱处理中忽略某些事件处理程序类型，或者想在这些配置中定义命名收件箱配置和分组事件处理程序，这将特别有用。

                                                            4、ImplementationType：

                                                                   实现收件箱数据库操作的类的类型。
                                                                   这通常在调用UseDbContext时设置，如前所示。
                                                                   有关高级用法，请参阅实现自定义发件箱/收件箱数据库提供程序部分。

                                                            5、DatabaseName：

                                                                   用于此发件箱配置的数据库的唯一库名。请参阅启用事件收件箱部分末尾的重要段落。

                                    5、AbpEventBusBoxesOptions 选项类
                                    
                                            AbpEventBusBoxesOptions 可用于微调收件箱和发件箱系统的工作方式。

                                            对于大多数系统使用默认值足够了，但是你可以在需要时对其进行配置以优化你的系统。

                                            就像所有选项类一样，AbpEventBusBoxesOptions可以在模块类的配置。如下以下代码块所示：

                                                Configure<AbpEventBusBoxesOptions>(options =>
                                                {
                                                    // TODO: configure the options
                                                });

                                            ★、AbpEventBusBoxesOptions具有以下要配置的属性：

                                                    ①、BatchPublishOutboxEvents：
                                                                可用于启用或禁用对消息代理的批量发布事件。
                                                                果分布式事件总线提供程序支持批量发布，则批量发布有效。
                                                                如果不支持，则事件将作为兜底逻辑一个接一个地发送。
                                                                保持启用状态，因为它尽可能提高性能。默认值为true（启用）

                                                    ②、PeriodTimeSpan：收件箱和发件箱消息处理器检查数据库中是否有新事件的周期。
                                                    
                                                                默认值为2秒

                                                    ③、CleanOldEventTimeIntervalSpan：

                                                                事件收件箱系统定期检查并从数据库中的收件箱中删除旧的已处理事件。
                                                                
                                                                你可以设置此值来确定检查周期。默认值为6小时（TimeSpan.FromHours(6)）。

                                                     ④、WaitTimeToDeleteProcessedInboxEvents：
                                                                 
                                                                收件箱事件在一段时间内即使处理成功也不会从数据库中删除，
                                                                
                                                                这是为了一个系统防止同一事件的多个进程（如果事件代理发送了两次），
                                                                
                                                                这个配置值决定了保留处理过的事件的时间，默认值为2小时（TimeSpan.FromHours(2)）。
                                                                
                                                     ⑤、InboxWaitingEventMaxCount：
                                                        
                                                                一次从数据库中的收件箱查询的最大事件数。默认值为1000。

                                                      ⑥、OutboxWaitingEventMaxCount：
                                                          
                                                                一次从数据库中的发件箱查询的最大事件数。默认值为1000。

                                                      ⑦、DistributedLockWaitDuration：
                                                      
                                                                当运行同一个应用程序的多个实例时，ABP使用分布式锁定来防止对数据库中的收件箱和发件箱消息的并发访问。
                                                                
                                                                如果应用程序的一个实例无法获得锁，它会在持续时间后尝试。这是该持续时间的配置。
                                                                
                                                                默认值为15秒（TimeSpan.FromSeconds(15)）。
                                                
                                                        
                                     6、跳过发件箱：

                                                    IDistributedEventBus.PublishAsync方法提供了一个可选参数，useOutbox，默认设置为true。

                                                    如果绕过发件箱并立即发布事件，则可以将其设置为false用于特定的事件发布操作。
                                                

                    */


        </script>
    </body>
</html>